import logging

from fastapi import APIRouter
from marshmallow import ValidationError
from starlette.websockets import WebSocket, WebSocketDisconnect

import settings
from apps.iacs.models import User
from utils.messages import PUBLIC_TOKEN
from utils.messages.serializers import HeaderErrorResponse
from utils.redis import Redis
from websocket.message_router import routes

logger = logging.getLogger('app.running.log')
websocket_router = APIRouter()


@websocket_router.websocket('/server/')
async def message_server(websocket: WebSocket):
    try:
        await connect(websocket)
        while True:
            content = await websocket.receive_json()
            msg_id, msg_type = content['msg_id'], content['msg_type']
            if msg_type in routes.keys():
                route = routes.get(msg_type)
                serializer_class, handler_class = route[:2]
                serializer = serializer_class()
                if not serializer.validate(content):
                    handler = handler_class(
                        channel_name=settings.MESSAGE_ROUTER,
                        channel_group_name=settings.MESSAGE_SERVER_GROUP_NAME,
                        route=route
                    )
                    data = await handler.do(**content, context=websocket)
                    data.update({'msg_id': msg_id})
                    await response(websocket, data, route)
                else:
                    await invalid_params(websocket, msg_id, serializer, route)
            else:
                await invalid_header(**{
                    'msg_id': msg_id,
                    'origin': content,
                    'errors': ValidationError({'msg_type': [f'错误的消息类型, {msg_type}']}).messages
                })
    except WebSocketDisconnect as e:
        logger.exception(e)


def get_token_in_header(ws: WebSocket):
    token = ws.scope.get('subprotocols')
    if isinstance(token, list):
        token = token[0]
    return token


async def authenticate(ws: WebSocket, token):
    if token != PUBLIC_TOKEN:
        key = settings.REDIS_LOGIN_TOKEN_KEY.format(token=token)
        async with Redis() as redis:
            try:
                user_id = await redis.hget(key, 'id')
                user_id = int(user_id)
                user = await User.get(id=user_id)
            except Exception as e:
                logger.error(f'描述: Websocket认证失败; Token: {token}')
                logger.exception(e)
                ws._raise_on_disconnect(message={'message': 'Invalid token'})
        if not user.is_active:
            logger.error(f'描述: 用户未激活; 用户ID: {user_id}')
            ws._raise_on_disconnect(message={'message': 'user is inactive.'})


async def connect(ws: WebSocket):
    try:
        token = get_token_in_header(ws)
        await authenticate(ws, token)
        await ws.accept(token)
    except WebSocketDisconnect:
        await ws.close()


async def invalid_header(ws: WebSocket, **kwargs):
    serializer = HeaderErrorResponse()
    if not serializer.validate(kwargs):
        await ws.send_json(kwargs)


async def invalid_params(ws: WebSocket, msg_id, serializer, route):
    response_serializer_class, _ = route[2:]
    await response(ws, {
        'msg_id': msg_id,
        'msg_type': response_serializer_class.Meta.MSG_TYPE,
        'errors': serializer.errors,
        'result_code': response_serializer_class.ResultCode.ILLEGAL_PARAMS
    }, route)


async def response(ws: WebSocket, data, route):
    serializer_class, handler_class = route[2:]
    response_serializer = serializer_class()
    if not response_serializer.validate(data):
        if handler_class is not None:
            handler = handler_class(channel_name=settings.MESSAGE_ROUTER,
                                    channel_group_name=settings.MESSAGE_SERVER_GROUP_NAME,
                                    route=route)
            result = await handler.do(serializer=response_serializer)
            return await ws.send_json(result)
        data.update({'msg_type': serializer_class.Meta.MSG_TYPE, 'status': 0})
        return await ws.send_json(data)


async def send_message(ws: WebSocket, data):
    """
    接收channel layer group send消息
    :param data:
    :return:
    """
    try:
        message = data.get('message')
        msg_type = message.get('msg_type')
        route = routes.get(msg_type)
        await response(ws, message, route)
    except Exception as e:
        logger.exception(e)
